import { computed, getCurrentInstance, inject, reactive, toRefs, watch } from "vue";
import { PiniaSymbol } from "./rootState";
import { activePinia } from "./createPinia";

function createOptionsStore(id, options, pinia) {
  const { state, actions, getters } = options;

  function setup(store) {
    pinia.state.value[id] = state ? state() : {};
    const localState = toRefs(pinia.state.value[id]);
    const setupStore = Object.assign(
      localState,
      actions,
      // 2、getters: 让getters中的this指向store
      Object.keys(getters).reduce((acc, key) => {
        acc[key] = computed(() => getters[key].call(store));
        return acc;
      }, {})
    );
    return setupStore;
  }

  return createCompositionStore(id, setup, pinia);
}

function createCompositionStore(id, setup, pinia, isCompositionSetup) {
  const partialStore = {
    // 订阅方法，监控数据变化执行回调
    $subscribe: (callback) => {
      watch(pinia.state.value[id], (state) => {
        callback(state);
      });
    },
  };

  const store = reactive(partialStore);
  const setupStore = setup(store);

  if (isCompositionSetup) {
    pinia.state.value[id] = {};
  }
  // 1、actions: 如果用户解构出方法调用，方法内部的this指向会变化，需要绑定this为store
  for (let prop in setupStore) {
    let value = setupStore[prop];
    if (typeof value === "function") {
      setupStore[prop] = value.bind(store);
    } else if (isCompositionSetup) {
      pinia.state.value[id][prop] = value;
    }
  }

  Object.assign(store, setupStore);
  pinia._p.forEach(plugin => {
      plugin({store, id});
  });
  pinia._s.set(id, store);

  return store;
}

export function defineStore(idOrOptions, setup) {
  let id;
  let options;

  //  区分是组合式 / 选项式
  let isCompositionSetup = typeof setup === "function";

  // defineStore的两种写法
  if (typeof idOrOptions === "string") {
    id = idOrOptions;
    options = setup;
  } else {
    id = idOrOptions.id;
    options = idOrOptions;
  }

  function useStore() {
    // 只能在组件中使用
    const currentInstance = getCurrentInstance();

    // 如果有执行上下问组件，就可以获取到全局实例，就获取到pinia；如果没有则直接获取暴露出来的pinia；
    const pinia = currentInstance ? inject(PiniaSymbol) : activePinia;

    if (!pinia._s.has(id)) {
      // 区分是组合式 / 选项式
      if (!isCompositionSetup) {
        createOptionsStore(id, options, pinia);
      } else {
        createCompositionStore(id, options, pinia, isCompositionSetup);
      }
    }
    const store = pinia._s.get(id);
    return store;
  }
  return useStore;
}
